﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using GameEngine;
using EngineTest2CLR;


namespace EngineTests2
{
    /// <summary>
    /// The main searching class
    /// </summary>
    public class Search
    {
        private const int GAME_LOSS = -65536;
        private CLREngineWrapper _wrapper;
        private int _logId;

        public Search(CLREngineWrapper wrapper, int logId)
        {
            _wrapper = wrapper;
            _logId = logId;
        }

        /// <summary>
        /// Really easy search algorithm for bitboard testing purpose only
        /// </summary>
        public int Negamax(GameManager mng, CLRBitBoard cbb, int depth, bool bWhite, out Move bestMove)
        {
            bestMove = null;           
            //  if depth finished, return evaluation
            if (depth == 0)
                return EvaluateGameBoard(mng.GameBoard, bWhite);

            // generate moves
            CLRBitBoard[] cbbMoves = _wrapper.EnglishBBGen(cbb, bWhite);
            List<Move> lstMoves = MoveGen.GenerateMoves(mng);

            // if no moves, inform about game loss
            if (lstMoves.Count == 0 && cbbMoves.Length == 0)
                return GAME_LOSS;

            // compare moves, if not successful, throw exception
            int[] moveMap;
            if (CompareMoves(lstMoves, cbbMoves, mng.GameBoard, cbb, out moveMap) == false)
                throw new ApplicationException("Error");

            // iterate through moves
            int bestResult = GAME_LOSS - 1;
            for (int i = 0; i < lstMoves.Count; i++)
            {
                // do move
                mng.DoMove(lstMoves[i]);                
                CLRBitBoard bbMove = cbbMoves[moveMap[i]];
                cbb.wm ^= bbMove.wm;
                cbb.bm ^= bbMove.bm;
                cbb.wk ^= bbMove.wk;
                cbb.bk ^= bbMove.bk;

                // call deeper
                Move moveDummy;
                int result = -Negamax(mng, cbb, depth - 1, !bWhite, out moveDummy);

                // undo move
                mng.MoveHistory.MoveBack();
                cbb.wm ^= bbMove.wm;
                cbb.bm ^= bbMove.bm;
                cbb.wk ^= bbMove.wk;
                cbb.bk ^= bbMove.bk;

                // if better result
                if (result > bestResult)
                {
                    bestResult = result;
                    bestMove = lstMoves[i];
                }             
            }                                 

            return bestResult;
        }


        /// <summary>
        /// Really easy search algorithm for bitboard testing purpose only - 64 bit version
        /// </summary>
        public int Negamax64(GameManager mng, CLRBitBoard64 cbb, int depth, bool bWhite, out Move bestMove)
        {
            bestMove = null;
            //  if depth finished, return evaluation
            if (depth == 0)
                return EvaluateGameBoard(mng.GameBoard, bWhite);

            // generate moves
            CLRBitBoard64[] cbbMoves = _wrapper.InternationalBBGen(cbb, bWhite);
            List<Move> lstMoves = MoveGen.GenerateMoves(mng, true);

            // if no moves, inform about game loss
            if (lstMoves.Count == 0 && cbbMoves.Length == 0)
                return GAME_LOSS;

            // compare moves, if not successful, throw exception
            int[] moveMap;
            if (CompareMoves64(lstMoves, cbbMoves, mng.GameBoard, cbb, mng, out moveMap) == false)
                throw new ApplicationException("Error");

            // iterate through moves
            int bestResult = GAME_LOSS - 1;
            for (int i = 0; i < lstMoves.Count; i++)
            {
                // do move
                mng.DoMove(lstMoves[i]);
                CLRBitBoard64 bbMove = cbbMoves[moveMap[i]];
                cbb.wm ^= bbMove.wm;
                cbb.bm ^= bbMove.bm;
                cbb.wk ^= bbMove.wk;
                cbb.bk ^= bbMove.bk;

                // call deeper
                Move moveDummy;
                int result = -Negamax64(mng, cbb, depth - 1, !bWhite, out moveDummy);

                // undo move
                mng.MoveHistory.MoveBack();
                cbb.wm ^= bbMove.wm;
                cbb.bm ^= bbMove.bm;
                cbb.wk ^= bbMove.wk;
                cbb.bk ^= bbMove.bk;

                // if better result
                if (result > bestResult)
                {
                    bestResult = result;
                    bestMove = lstMoves[i];
                }
            }

            return bestResult;
        }


        /// <summary>
        /// Test method for comparing moves
        /// </summary>
        private bool CompareMoves(List<Move> lstMoves, CLRBitBoard[] cbbMoves, GameBoard gameBoard, CLRBitBoard bbGameBoard, out int[] map)
        {
            map = null;
            // convert moves to bitboards
            List<CLRBitBoard> lstBB = BitBoardConverter.ConvertMoves(lstMoves, gameBoard);

            // if the numbers of moves are not equal
            if (lstMoves.Count != cbbMoves.Length)
            {
                _wrapper.LogMessage(_logId, "Board situation:\n");
                _wrapper.LogBB(_logId, bbGameBoard);

                _wrapper.LogMessage(_logId, "Error: different numbers of moves.\n");

                // log Game Engine moves
                _wrapper.LogMessage(_logId, "GameEngine moves:\n");
                foreach (var move in lstBB)
                {
                    _wrapper.LogBB(_logId, move);
                }

                // log BitBoard moves
                _wrapper.LogMessage(_logId, "BitBoard moves:\n");
                foreach (var move in cbbMoves)
                {
                    _wrapper.LogBB(_logId, move);
                }

                return false;                 
            }

            bool bResult = true;
            map = new int[lstMoves.Count];

            int gmIndex = 0;
            bool bBoardLogged = false;
            foreach (var bb in lstBB)
            {
                bool bFound = false;
                int bbIndex = 0;
                foreach (var cbb in cbbMoves)
                {
                    if (cbb.wm == bb.wm && cbb.bm == bb.bm && bb.wk == cbb.wk && bb.bk == cbb.bk)
                    {
                        map[gmIndex] = bbIndex;
                        // if move found
                        bFound = true;
                        break;
                    }

                    bbIndex++;
                }

                if (bFound == false)
                {
                    if (bBoardLogged == false)
                    {
                        _wrapper.LogMessage(_logId, "Board situation:\n");
                        _wrapper.LogBB(_logId, bbGameBoard);
                        bBoardLogged = true;
                    }

                    _wrapper.LogMessage(_logId, "Move not found:\n");
                    _wrapper.LogBB(_logId, bb);
                    bResult = false;
                }

                gmIndex++;
            }

            return bResult;
        }


        /// <summary>
        /// Test method for comparing moves - 64 bit version
        /// </summary>
        private bool CompareMoves64(List<Move> lstMoves, CLRBitBoard64[] cbbMoves, GameBoard gameBoard, CLRBitBoard64 bbGameBoard, GameManager mng, out int[] map)
        {
            map = null;
            // convert moves to bitboards
            List<CLRBitBoard64> lstBB = BitBoardConverter.ConvertMoves64(lstMoves, gameBoard);

            // if the numbers of moves are not equal
            if (lstMoves.Count != cbbMoves.Length)
            {
                List<Move> dummyList = MoveGen.GenerateMoves(mng, true);

                _wrapper.LogMessage(_logId, "GameBoard situation:\n");
                CLRBitBoard64 bbGB = BitBoardConverter.ConvertGameBoard64(gameBoard);
                _wrapper.LogBB64(_logId, bbGB);

                _wrapper.LogMessage(_logId, "BitBoard situation:\n");
                _wrapper.LogBB64(_logId, bbGameBoard);

                _wrapper.LogMessage(_logId, "Error: different numbers of moves.\n");

                // log Game Engine moves
                _wrapper.LogMessage(_logId, "GameEngine moves:\n");
                foreach (var move in lstBB)
                {
                    _wrapper.LogBB64(_logId, move);
                }

                // log BitBoard moves
                _wrapper.LogMessage(_logId, "BitBoard moves:\n");
                foreach (var move in cbbMoves)
                {
                    _wrapper.LogBB64(_logId, move);
                }

                return false;
            }

            bool bResult = true;
            map = new int[lstMoves.Count];

            int gmIndex = 0;
            bool bBoardLogged = false;

            foreach (var bb in lstBB)
            {
                bool bFound = false;
                int bbIndex = 0;
                foreach (var cbb in cbbMoves)
                {
                    if (cbb.wm == bb.wm && cbb.bm == bb.bm && bb.wk == cbb.wk && bb.bk == cbb.bk)
                    {
                        map[gmIndex] = bbIndex;
                        // if move found
                        bFound = true;
                        break;
                    }

                    bbIndex++;
                }

                if (bFound == false)
                {
                    if (bBoardLogged == false)
                    {
                        _wrapper.LogMessage(_logId, "Board situation:\n");
                        _wrapper.LogBB64(_logId, bbGameBoard);
                        bBoardLogged = true;
                    }

                    _wrapper.LogMessage(_logId, "Move not found:\n");
                    _wrapper.LogBB64(_logId, bb);
                    bResult = false;
                }

                gmIndex++;
            }

            return bResult;
        }


        
        /// <summary>
        /// Returns board evaluation
        /// </summary>
        private int EvaluateGameBoard(GameBoard gameBoard, bool bWhite)
        {
            const int manValue = 100;
            const int kingValue = 250;

            int white = 0;
            int black = 0;
            for (int row = 0; row < gameBoard.Size; row++)
            {
                for (int col = 0; col < gameBoard.Size; col++)
                {
                    enumFigureType figType = gameBoard.GetField(row, col);

                    if (figType != enumFigureType.Nothing)
                    {
                        if (figType.IsKing())
                        {
                            if (figType.FigureColor() == enumFigureColor.White)
                                white += kingValue;
                            else
                                black += kingValue;

                        }
                        else
                        {
                            if (figType.FigureColor() == enumFigureColor.White)
                                white += manValue + row;
                            else
                                black += manValue + (gameBoard.Size - row - 1);
                        }
                    }
                }
            }

            return bWhite ? white - black : black - white;
        }
    }
}
